#include <stx/test/test.hpp>

TEST_CASE (core_net)
{
    CHECK(!queryDns("facebook.com"_sv, IpType::K_A).get().empty());
    CHECK(!queryDns("facebook.com"_sv, IpType::K_AAAA).get().empty());
    CHECK(queryDns("facebook.com"_sv, IpType::K_ALL).get().size() >= 2);

    CHECK(!DnsCache::instance()
               .query("facebook.com"_sv, IpType::K_A)
               .get()
               .empty());
    CHECK(!DnsCache::instance()
               .query("facebook.com"_sv, IpType::K_AAAA)
               .get()
               .empty());
    CHECK(DnsCache::instance()
              .query("facebook.com"_sv, IpType::K_ALL)
              .get()
              .size() >= 2);

    auto const ip6_bin = std::vector<std::uint8_t>{0x20,
                                                   0x01,
                                                   0x0d,
                                                   0xb8,
                                                   0x85,
                                                   0xa3,
                                                   0x00,
                                                   0x00,
                                                   0x00,
                                                   0x00,
                                                   0x8a,
                                                   0x2e,
                                                   0x03,
                                                   0x70,
                                                   0x73,
                                                   0x34};
    auto const s_ip4   = "1.2.3.4";
    auto const s_ip6   = "2001:0db8:85a3:0000:0000:8a2e:0370:7334";
    auto const ip4_obj = "1.2.3.4"_ip4;
    auto const ip6_obj = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"_ip6;
    auto const sv_ep4  = "1.2.3.4:1234"sv;
    auto const sv_ep6  = "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:1234"sv;

    CHECK(MemView(0x04030201_u32) == MemView(*getIp4(s_ip4)));
    CHECK(MemView(ip6_bin) == MemView(*getIp6(s_ip6)));

    CHECK(MemView(0x04030201_u32) == MemView(ip4_obj));
    CHECK(MemView(ip6_bin) == MemView(ip6_obj));

    {
        auto ep = Endpoint{};
        CHECK(!ep);

        ep = Endpoint(ip4_obj, Port(1234));
        CHECK(ep);
        CHECK(ep.isIp4());
        CHECK(!ep.isIp6());
        CHECK(ep.port() == 1234);

        auto const sa = ep.getSockAddr();
        CHECK(AF_INET == sa->sa_family);
        auto sin = std::bit_cast<::sockaddr_in const*>(sa);
        CHECK(1234_u16 == std::byteswap(sin->sin_port));
        CHECK(MemView(0x04030201_u32) == MemView(sin->sin_addr));
        CHECK(MemView(0x04030201_u32) == MemView(ep.ip4()));

        auto ep2 = Endpoint(*sa);
        auto ep3 = Endpoint(*sin);

        CHECK(ep2.isIp4());
        CHECK(ep3.isIp4());
        CHECK(ep.ip4() == ep2.ip4());
        CHECK(ep.ip4() == ep3.ip4());
        CHECK(ep.port() == ep2.port());
        CHECK(ep.port() == ep3.port());
    }

    {
        auto ep = Endpoint{};
        CHECK(!ep);

        ep = Endpoint(ip6_obj, Port(1234));
        CHECK(ep);
        CHECK(!ep.isIp4());
        CHECK(ep.isIp6());
        CHECK(ep.port() == 1234);

        auto const sa = ep.getSockAddr();
        CHECK(AF_INET6 == sa->sa_family);
        auto sin6 = std::bit_cast<::sockaddr_in6 const*>(sa);
        CHECK(1234_u16 == std::byteswap(sin6->sin6_port));
        CHECK(MemView(ip6_bin) == MemView(sin6->sin6_addr));
        CHECK(MemView(ip6_bin) == MemView(ep.ip6()));

        auto ep2 = Endpoint(*sa);
        auto ep3 = Endpoint(*sin6);

        CHECK(ep2.isIp6());
        CHECK(ep3.isIp6());
        CHECK(ep.ip6() == ep2.ip6());
        CHECK(ep.ip6() == ep3.ip6());
        CHECK(ep.port() == ep2.port());
        CHECK(ep.port() == ep3.port());
    }

    {
        auto ep = getEndpoint("facebook.com"s, Port(1234)).get();
        CHECK(ep);
        CHECK(ep.port() == 1234);
    }

    {
        auto ep = getEndpoint("facebook.com:1234"sv).get();
        CHECK(ep);
        CHECK(ep.port() == 1234);
    }

    {
        auto ep = getEndpoint("1.2.3.4"s, Port(1234)).get();
        CHECK(ep);
        CHECK(ep.isIp4());
        CHECK(!ep.isIp6());
        CHECK(ep.port() == 1234);

        auto const sa = ep.getSockAddr();
        CHECK(AF_INET == sa->sa_family);
        auto sin = std::bit_cast<::sockaddr_in const*>(sa);
        CHECK(1234_u16 == std::byteswap(sin->sin_port));
        CHECK(MemView(0x04030201_u32) == MemView(sin->sin_addr));
        CHECK(MemView(0x04030201_u32) == MemView(ep.ip4()));

        auto ep2 = Endpoint(*sa);
        auto ep3 = Endpoint(*sin);

        CHECK(ep2.isIp4());
        CHECK(ep3.isIp4());
        CHECK(ep.ip4() == ep2.ip4());
        CHECK(ep.ip4() == ep3.ip4());
        CHECK(ep.port() == ep2.port());
        CHECK(ep.port() == ep3.port());
    }

    {
        auto ep = getEndpoint("1.2.3.4:1234"sv).get();
        CHECK(ep);
        CHECK(ep.isIp4());
        CHECK(!ep.isIp6());
        CHECK(ep.port() == 1234);

        auto const sa = ep.getSockAddr();
        CHECK(AF_INET == sa->sa_family);
        auto sin = std::bit_cast<::sockaddr_in const*>(sa);
        CHECK(1234_u16 == std::byteswap(sin->sin_port));
        CHECK(MemView(0x04030201_u32) == MemView(sin->sin_addr));
        CHECK(MemView(0x04030201_u32) == MemView(ep.ip4()));

        auto ep2 = Endpoint(*sa);
        auto ep3 = Endpoint(*sin);

        CHECK(ep2.isIp4());
        CHECK(ep3.isIp4());
        CHECK(ep.ip4() == ep2.ip4());
        CHECK(ep.ip4() == ep3.ip4());
        CHECK(ep.port() == ep2.port());
        CHECK(ep.port() == ep3.port());
    }

    {
        auto ep = getEndpoint(sv_ep6).get();
        CHECK(ep);
        CHECK(!ep.isIp4());
        CHECK(ep.isIp6());
        CHECK(ep.port() == 1234);

        auto const sa = ep.getSockAddr();
        CHECK(AF_INET6 == sa->sa_family);
        auto sin6 = std::bit_cast<::sockaddr_in6 const*>(sa);
        CHECK(1234_u16 == std::byteswap(sin6->sin6_port));
        CHECK(MemView(ip6_bin) == MemView(sin6->sin6_addr));
        CHECK(MemView(ip6_bin) == MemView(ep.ip6()));

        auto ep2 = Endpoint(*sa);
        auto ep3 = Endpoint(*sin6);

        CHECK(ep2.isIp6());
        CHECK(ep3.isIp6());
        CHECK(ep.ip6() == ep2.ip6());
        CHECK(ep.ip6() == ep3.ip6());
        CHECK(ep.port() == ep2.port());
        CHECK(ep.port() == ep3.port());
    }

    auto const ep4 = Endpoint(ip4_obj, Port(1234));
    auto const ep6 = Endpoint(ip6_obj, Port(1234));

    CHECK("1234"sv == tos(Port(1234)));
    CHECK("1.2.3.4"sv == tos("1.2.3.4"_ip4));

    CHECK("2001:0db8:85a3:0000:0000:8a2e:0370:7334"sv == tos(ip6_obj) ||
          "2001:0db8:85a3::8a2e:0370:7334"sv == tos(ip6_obj) ||
          "2001:db8:85a3::8a2e:370:7334"sv == tos(ip6_obj));

    CHECK(sv_ep4 == tos(ep4));
    CHECK("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:1234"sv == tos(ep6) ||
          "[2001:0db8:85a3::8a2e:0370:7334]:1234"sv == tos(ep6) ||
          "[2001:db8:85a3::8a2e:370:7334]:1234"sv == tos(ep6));

    CHECK("1.2.3.4"sv == getIpText(*ep4.getSockAddr()));
    CHECK("2001:0db8:85a3:0000:0000:8a2e:0370:7334"sv ==
              getIpText(*ep6.getSockAddr()) ||
          "2001:0db8:85a3::8a2e:0370:7334"sv == getIpText(*ep6.getSockAddr()) ||
          "2001:db8:85a3::8a2e:370:7334"sv == getIpText(*ep6.getSockAddr()));
    CHECK("1234"sv == getPortText(*ep4.getSockAddr()));
    CHECK("1234"sv == getPortText(*ep6.getSockAddr()));

    auto const cnt = 10000uz;

    {
        auto pkt_size     = std::atomic_size_t(0);
        auto local_ip_obj = "127.0.0.1"_ip4;
        auto local_ep     = Endpoint(local_ip_obj, Port(64000));

        auto fn_server = [&] -> Future<>
        {
            auto svr  = UdpServer(local_ep);
            auto bufs = std::vector<Buffer>(128);
            auto mss  = std::vector<MemSpan>(128);
            auto i    = 0uz;

            for (auto& buf : bufs)
            {
                buf.resize(1500);
                mss[i++] = buf;
            }

            while (true)
            {
                auto [status, total_size, msgs] = co_await svr.recv(mss);
                CHECK(C_STATUS_SUCCESS == status);
                CHECK(ranges::all_of(msgs,
                                     [&](auto const& msg)
                                     {
                                         return msg.remote_ep.ip4() ==
                                                    local_ip_obj &&
                                                MemView(msg.ms) ==
                                                    MemView("hello"sv);
                                     }));
            }
        };

        fn_server();
        sleepFor(100ms);

        auto fn_coro_client = [&] -> Future<>
        {
            auto client = co_await startUdpClient(local_ep);
            CHECK(client);
            CHECK(5 == co_await client.send("hello"sv));
            ++pkt_size;
        };

        LOOP_N (cnt)
        {
            fn_coro_client();
        }

        while (pkt_size < cnt)
        {
            sleepFor(200ms);
        }
    }

    {
        auto pkt_size     = std::atomic_size_t(0);
        auto local_ip_obj = "127.0.0.1"_ip4;
        auto local_ep     = Endpoint(local_ip_obj, Port(64001));

        auto const fn_server = [&] -> Future<>
        {
            auto svr = UdpServer(local_ep);
            auto buf = Buffer(1500);

            while (true)
            {
                auto [status, msg] = co_await svr.recv(buf);
                CHECK(C_STATUS_SUCCESS == status);
                CHECK(msg.remote_ep.ip4() == local_ip_obj);
                CHECK(MemView(msg.ms) == MemView("hello"sv));
            }
        };

        fn_server();
        sleepFor(100ms);

        auto fn_coro_client = [&] -> Future<>
        {
            auto client = co_await startUdpClient(local_ep);
            CHECK(client);
            CHECK(5 == co_await client.send("hello"sv));

            ++pkt_size;
        };

        LOOP_N (cnt)
        {
            fn_coro_client();
        }

        while (pkt_size < cnt)
        {
            sleepFor(200ms);
        }
    }

    {
        auto pkt_size     = std::atomic_size_t(0);
        auto local_ip_obj = "127.0.0.1"_ip4;
        auto local_ep     = Endpoint(local_ip_obj, Port(64002));

        auto fn_on_new_conn = [&](TcpSocket      tcp_skt,
                                  RemoteEndpoint remote_ep,
                                  LocalEndpoint  local_ep) -> Future<>
        {
            auto buf = Buffer(128);
            co_await tcp_skt.read(buf);
            co_await tcp_skt.write("abc123"sv);
        };

        startTcpServer(local_ep, fn_on_new_conn);
        sleepFor(100ms);

        auto fn_coro_client = [&] -> Future<>
        {
            auto client = co_await startTcpClient(local_ep);
            CHECK(client);
            CHECK(5 == co_await client.write("hello"sv, 1s));

            auto buf = Buffer(128);
            CHECK(6 == co_await client.read(buf, 1s));
            CHECK(MemView(buf.data(), 6) == MemView("abc123"sv));
            ++pkt_size;
        };

        LOOP_N (cnt)
        {
            fn_coro_client();
        }

        while (pkt_size < cnt)
        {
            sleepFor(200ms);
        }
    }
}